﻿// --------------------------------------------------------------------------------------------------------------------
// <copyright file="MappingExtensions.cs" >
//   J. Prinz
// </copyright>
// <summary>
//   Specific Extensions for Mappings
//   <para> author: J. Prinz </para>
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace AutomationMLMapper.AutomationML.Extensions
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Linq;

    using AMLEngineExtensions;

    using CAEX_ClassModel;

    /// <summary>
    ///   Specific Extensions for Mappings
    ///   <para> author: J. Prinz </para>
    /// </summary>
    public static class MappingExtension
    {
        #region Static Fields

        /// <summary>
        ///   the Reference for the standard PropertySet RoleClass in AutomationML
        /// </summary>
        public static string AutomationMLPropertySetRoleClassReference =
            "AutomationMLBaseRoleClassLib/AutomationMLBaseRole/PropertySet";

        #endregion

        #region Public Methods and Operators

        /// <summary>
        /// Find a Caex-Object by Name in the provided Collection of CAEXObjects
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="caexObjectCollection">
        /// </param>
        /// <param name="name">
        /// </param>
        /// <returns>
        /// The System.Boolean. 
        /// </returns>
        public static CAEXObject FindCaexObjectByName(this IEnumerable caexObjectCollection, string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("Value of input parameter name should not be null or empty");
            }

            return
                caexObjectCollection.Cast<CAEXObject>().SingleOrDefault(
                    caexObject => caexObject.Name.Exists() && caexObject.Name.Value == name);
        }

        /// <summary>
        /// Gets all Internal Elements of a SystemUnitClass and its SubElements
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="systemUnitClass">
        /// the system Unit Class, which is scanned deep for all Internal Elements 
        /// </param>
        /// <returns>
        /// The System.Collections.Generic.IEnumerable`1[T - &gt; CAEX_ClassModel.InternalElementType]. 
        /// </returns>
        public static IEnumerable<InternalElementType> GetAllInternalElementsDeep(
            this SystemUnitClassType systemUnitClass)
        {
            foreach (InternalElementType internalElement in systemUnitClass.InternalElement)
            {
                yield return internalElement;
                foreach (var childInternalElement in GetAllInternalElementsDeep(internalElement))
                {
                    yield return childInternalElement;
                }
            }
        }

        /// <summary>
        /// Get all InternalElements of the SystemUnitClass, which are PropertySets, regardless of there nesting level
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="systemUnitClass">
        /// a SystemUnitClass 
        /// </param>
        /// <returns>
        /// Enumerator for the PropertySets of the SystemUnitClass 
        /// </returns>
        public static IEnumerable<InternalElementType> GetAllPropertySetElementsDeep(
            this SystemUnitClassType systemUnitClass)
        {
            return
                systemUnitClass.GetAllInternalElementsDeep().Where(
                    internalElement => internalElement.IsPropertySetElement());
        }

        /// <summary>
        /// Get a Caex-Object by Name in the provided Collection of CAEXObjects
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="caexObjectCollection">
        /// </param>
        /// <param name="name">
        /// </param>
        /// <returns>
        /// The CAEX_ClassModel.CAEXObject. 
        /// </returns>
        public static CAEXObject GetCaexObjectByName(this IEnumerable caexObjectCollection, string name)
        {
            if (string.IsNullOrEmpty(name))
            {
                throw new ArgumentException("Value of input parameter name should not be null or empty");
            }

            return
                caexObjectCollection.Cast<CAEXObject>().FirstOrDefault(
                    caexObject => caexObject.Name.Exists() && caexObject.Name.Value == name);
        }

        /// <summary>
        /// Get all Attributes of the CAEXObjects, including all own and derived attributes
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="objectWithAttributes">
        /// a CaexObject which implements the IObjectWithAttributes Interface 
        /// </param>
        /// <returns>
        /// Enumerator for the all Attributes of the SystemUnitClass (own and derived) 
        /// </returns>
        public static IEnumerable<AttributeType> GetOwnAndDerivedAttributes(
            this IObjectWithAttributes objectWithAttributes)
        {
            var derivedAttributes = objectWithAttributes.GetDerivedAttributes();

            foreach (AttributeType attribute in objectWithAttributes.getAttributeField())
            {
                derivedAttributes.RemoveAll(a => a.Name.Value == attribute.Name.Value);
                yield return attribute;
            }

            foreach (var attribute in derivedAttributes)
            {
                yield return attribute;
            }
        }

        /// <summary>
        /// An InternalElement is searched for a MappingObject, which has an AttributeNameMapping with a SystemUnitAttributeName, which is equal to the
        ///   name of the input attribute
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="internalElement">
        /// the InternalElement which is searched for a mappingObject 
        /// </param>
        /// <param name="attribute">
        /// the input Attribute for which a matching AttributeNameMapping is searched 
        /// </param>
        /// <returns>
        /// true, if a matching AttributeNameMapping is found 
        /// </returns>
        public static bool HasMappingToSystemUnitAttribute(
            this InternalElementType internalElement, AttributeType attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }

            if (!attribute.Name.Exists())
            {
                throw new ArgumentException("attribute input parameter has no value for name");
            }

            if (internalElement.MappingObject.Exists)
            {
                return
                    internalElement.MappingObject.Cast<MappingType>().Any(
                        mappingObject =>
                        mappingObject.AttributeNameMapping.Exists
                        && mappingObject.AttributeNameMapping[0].SystemUnitAttributeName.Value == attribute.Name.Value);
            }

            return false;
        }

        /// <summary>
        /// Test, if an InternalElements RoleRequirement ist directly supported from any SupportedRole of the SystemUnitClass
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="internalElement">
        /// The InternalElement which should be mapped on a SystemUnitClass 
        /// </param>
        /// <param name="systemUnitClass">
        /// The SystemUnitClass, which is tested as a Mapping Candidate 
        /// </param>
        /// <returns>
        /// The System.Boolean. 
        /// </returns>
        public static bool InternalElementDirectlyMappsToSystemUnitClass(
            this InternalElementType internalElement, SystemUnitClassType systemUnitClass)
        {
            if (systemUnitClass == null)
            {
                throw new ArgumentNullException("systemUnitClass");
            }

            if (internalElement.RoleRequirements.Exists)
            {
                if (internalElement.RoleRequirements[0].RefBaseRoleClassPath.Exists())
                {
                    var rolePath = internalElement.RoleRequirements[0].RefBaseRoleClassPath.Value;

                    if (systemUnitClass.SupportedRoleClass.Exists)
                    {
                        return
                            systemUnitClass.SupportedRoleClass.Cast<SupportedRoleClassType>().Where(
                                supportedRole => supportedRole.RefRoleClassPath.Exists()).Any(
                                    supportedRole => rolePath == supportedRole.RefRoleClassPath.Value);
                    }
                }
            }

            return false;
        }

        /// <summary>
        /// Test, if an InternalElements RoleRequirement ist supported from any SupportedRole of the SystemUnitClass
        ///   and the SupportedRole is an Ancestor of the Required Role. The returned result is the Ancestor Generation
        ///   of the Supported Role (-1=no Ancestor, 0=Identical, 1=Parent, 2=GrandParent, ...)
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="internalElement">
        /// The InternalElement which should be mapped on a SystemUnitClass 
        /// </param>
        /// <param name="systemUnitClass">
        /// The SystemUnitClass, which is tested as a Mapping Candidate 
        /// </param>
        /// <param name="supportedRole">
        /// The SupportedRole of the SystemUnitClass, which is the nearest generation to the required role 
        /// </param>
        /// <returns>
        /// Ancestor Generation 
        /// </returns>
        public static int InternalElementMappsToSystemUnitClassWithAncestorRole(
            this InternalElementType internalElement, 
            SystemUnitClassType systemUnitClass, 
            out SupportedRoleClassType supportedRole)
        {
            var generation = int.MaxValue;
            RoleFamilyType requiredRole = null;
            supportedRole = null;

            if (systemUnitClass == null)
            {
                throw new ArgumentNullException("systemUnitClass");
            }

            if (internalElement.RoleRequirements.Exists)
            {
                if (internalElement.RoleRequirements[0].RefBaseRoleClassPath.Exists())
                {
                    var rolePath = internalElement.RoleRequirements[0].RefBaseRoleClassPath.Value;

                    if (systemUnitClass.SupportedRoleClass.Exists)
                    {
                        foreach (SupportedRoleClassType supportedRoleClass in systemUnitClass.SupportedRoleClass)
                        {
                            if (supportedRoleClass.RefRoleClassPath.Exists())
                            {
                                if (rolePath == supportedRoleClass.RefRoleClassPath.Value)
                                {
                                    supportedRole = supportedRoleClass;
                                    return 0;
                                }

                                // if the required Role is not defined, we'll try to get it now
                                if (requiredRole == null)
                                {
                                    requiredRole = internalElement.RoleRequirements[0].getReferencedRoleClass(rolePath);
                                    if (requiredRole == null)
                                    {
                                        return -1;
                                    }
                                }

                                // traverse the generation path to find an ancestor, which mapps to the supported Role
                                var roleGenerations = requiredRole.GetReferenceHierarchy(requiredRole);
                                var ancestorGeneration = -1;
                                var foundMatch = false;

                                while (roleGenerations.Count > 0 && !foundMatch)
                                {
                                    var ancestorRole = roleGenerations.Dequeue();

                                    ancestorGeneration++;

                                    // test, if we have found a match to the supported Role
                                    if ((ancestorRole as RoleClassType).CAEXPath()
                                        == supportedRoleClass.RefRoleClassPath.Value)
                                    {
                                        // if this is the nearest generation, we have found until now, hold it an continue with outer loop
                                        if (ancestorGeneration < generation)
                                        {
                                            generation = ancestorGeneration;
                                            supportedRole = supportedRoleClass;
                                            foundMatch = true;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    // we have come from the foreach loop with a result 
                    if (supportedRole != null)
                    {
                        return generation;
                    }
                }
            }

            return -1;
        }

        /// <summary>
        /// An InternalElement is checked, whether its required role is derived from the AutomationML PropertySet RoleClass. This
        ///   RoleClass is identified via its Path which should be <see cref="AutomationMLPropertySetRoleClassReference"/>
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="internalElement">
        /// An Internal Element 
        /// </param>
        /// <returns>
        /// true, if the Internal Elements required Role is derived from the AutomationML BaseRoleClass "PropertySet"; false otherwise 
        /// </returns>
        public static bool IsPropertySetElement(this InternalElementType internalElement)
        {
            if (internalElement.RoleRequirements.Exists
                && !string.IsNullOrEmpty(internalElement.RoleRequirements[0].RefBaseRoleClassPath.Value))
            {
                var roleFamilyType =
                    internalElement.RoleRequirements[0].getReferencedRoleClass(
                        internalElement.RoleRequirements[0].RefBaseRoleClassPath.Value);
                if (roleFamilyType != null)
                {
                    return roleFamilyType.IsPropertySetRoleClass();
                }
            }

            return false;
        }

        /// <summary>
        /// This Methods tests. if a role class is derived from the AutomationMLBaseRoleClass - PropertySet
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="roleClass">
        /// Testcandidate role Class 
        /// </param>
        /// <returns>
        /// true, if it is a PropertySet 
        /// </returns>
        public static bool IsPropertySetRoleClass(this RoleFamilyType roleClass)
        {
            var roleGenerations = roleClass.GetReferenceHierarchy(roleClass);
            do
            {
                var path = (roleGenerations.Dequeue() as RoleClassType).CAEXPath();
                if (path == AutomationMLPropertySetRoleClassReference)
                {
                    return true;
                }
            }
            while (roleGenerations.Count > 0);
            return false;
        }

        /// <summary>
        /// Extension Method for any CAEXBasicObject returns the Name of the XMLNode for that Object
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="caex">
        /// CAEX-Element 
        /// </param>
        /// <returns>
        /// Name of the XMLNode of the CAEXBasicObject 
        /// </returns>
        public static string Name(this CAEXBasicObject caex)
        {
            return caex.Node.Name;
        }

        /// <summary>
        /// Extension Method for any CAEXObject returns the Name of the CAEXObject
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="caex">
        /// CAEX-Element 
        /// </param>
        /// <returns>
        /// Name of the CAEXObject 
        /// </returns>
        public static string Name(this CAEXObject caex)
        {
            return caex.Name.Exists() ? caex.Name.Value : "";
        }

        /// <summary>
        /// This Extension Methods performs a search on the systemUnitClass and its elements for any InternalElement with an assigned PropertySet and an
        ///   assigned MappingObject and AttributeNameMapping, which has a SystemUnitAttributeName, which maps to the Name of the input attribute.
        ///   It returns the InternalElement Object, if it is found, otherwise the result is null
        ///   <para>
        /// author: J. Prinz 
        /// </para>
        /// </summary>
        /// <param name="systemUnitClass">
        /// The SystemUnitClass to which the input attribute belongs to 
        /// </param>
        /// <param name="attribute">
        /// The input attribute for which an attribute Name Mapping is searched 
        /// </param>
        /// <returns>
        /// The InternalElement which has the matching AttributeNameMapping for the input attribute or null if not found 
        /// </returns>
        public static InternalElementType PropertySetAttributeMapping(
            this SystemUnitClassType systemUnitClass, AttributeType attribute)
        {
            if (attribute == null)
            {
                throw new ArgumentNullException("attribute");
            }

            if (!attribute.Name.Exists())
            {
                throw new ArgumentException("attribute input parameter has no value for name");
            }

            if (systemUnitClass.InternalElement.Exists)
            {
                return
                    systemUnitClass.GetAllInternalElementsDeep().FirstOrDefault(
                        internalElement =>
                        internalElement.IsPropertySetElement()
                        && internalElement.HasMappingToSystemUnitAttribute(attribute));
            }

            return null;
        }

        #endregion
    }
}